package com.xiezc.springboot_dht.service;

import com.xiezc.springboot_dht.entityx.Bucket;
import com.xiezc.springboot_dht.entityx.Node;
import com.xiezc.springboot_dht.message.Message;
import com.xiezc.springboot_dht.message.MessageBuild;
import com.xiezc.springboot_dht.message.MsgTypeEnum;
import com.xiezc.springboot_dht.model.BeMap;
import com.xiezc.springboot_dht.model.BeString;
import com.xiezc.springboot_dht.util.Const;
import com.xiezc.springboot_dht.util.XUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutionException;

/**
 * 处理接收到的消息的线程
 *
 * @author wb-xzc291800
 * @date 2018/05/09 15:12
 */
@Component
@Slf4j
public class MessageManager {

    private BlockingQueue<Message> sendCache;

    public MessageManager(BlockingQueue<Message> sendCache) {
        this.sendCache = sendCache;
    }

    /**
     * 收到的不同消息的处理方法
     *
     * @param message
     */
    public void execute(Message message) {
        try {
            BeMap beMap = message.getBeMap();
            InetSocketAddress socketAddress = message.getSocketAddress();
            MsgTypeEnum msgTypeEnum = message.getMsgTypeEnum();
            switch (msgTypeEnum) {
                case ping:
                    dealPing(beMap, socketAddress);
                    break;
                case findNode:
                    dealFindNode(beMap, socketAddress);
                    break;
                case getPeers:
                    dealGetPeers(beMap, socketAddress);
                    break;
                case annountcePeer:
                    log.info("接收的的是annountcePeer消息, 不处理 ......... ", beMap);
                    break;
                case resp_ping:
                    dealRespPing(beMap, socketAddress);
                    break;
                case resp_findNode:
                    dealRespFindNode(beMap, socketAddress);
                    break;
                case resp_getPeers:
                    log.info("本项目目前还不会发送getpeers请求, 所以对于getpeers的响应不予理会");
                    break;
                default:
                    break;
            }

        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
        }

    }

    /**
     * 处理ping的响应
     *
     * @param beMap
     * @param socketAddress
     */
    private void dealRespFindNode(BeMap beMap, InetSocketAddress socketAddress) {
       // log.debug("dealRespFindNode {}",socketAddress);
        BeMap rBeMap = (BeMap)beMap.get("r");
        BeString id = (BeString)rBeMap.get("id");
        BeString nodes = (BeString)rBeMap.get("nodes");
        byte[] nodesValue = nodes.getValue();
        int len = nodesValue.length / 26;

        Node.createNode(id, socketAddress);

        for (int i = 0; i < len; i++) {
            byte[] idN = new byte[20];
            System.arraycopy(nodesValue, i * 26 + 0, idN, 0, 20);
            BeString idNS = new BeString(idN);

            byte[] idIp = new byte[4];
            System.arraycopy(nodesValue, i * 26 + 20, idIp, 0, 4);
            String ip = getIp(idIp);

            byte[] idPort = new byte[2];
            System.arraycopy(nodesValue, i * 26 + 24, idPort, 0, 2);
            int port = ((0xff & idPort[0]) << 8) + (0xff & idPort[1]);

            Message message = MessageBuild.buildReqMsg()
                .buildAId(Const.thisId)
                .buildPing(ip, port);
            sendCache.add(message);
        }
    }

    /**
     * 将ip地址的字节表示方式转换成字符串的形式
     *
     * @param idIp
     * @return
     */
    private String getIp(byte[] idIp) {
        int i2;
        if (idIp[0] <= 0) {
            i2 = idIp[0] + 128 * 2;
        } else {
            i2 = idIp[0];
        }
        int i3;
        if (idIp[1] <= 0) {
            i3 = idIp[1] + 128 * 2;
        } else {
            i3 = idIp[1];
        }
        int i4;
        if (idIp[2] <= 0) {
            i4 = idIp[2] + 128 * 2;
        } else {
            i4 = idIp[2];
        }
        int i5;
        if (idIp[3] <= 0) {
            i5 = idIp[3] + 128 * 2;
        } else {
            i5 = idIp[3];
        }
        return i2 + "." + i3 + "." + i4 + "." + i5;
    }

    /**
     * 处理ping的响应
     *
     * @param beMap
     * @param socketAddress
     */
    private void dealRespPing(BeMap beMap, InetSocketAddress socketAddress) {
        //log.debug("dealRespPing {}",socketAddress);
        BeMap rBeMap = (BeMap)beMap.get("r");
        BeString id = (BeString)rBeMap.get("id");
        Node.createNode(id, socketAddress);
    }

    /**
     * @param beMap
     * @param socketAddress
     */
    private void dealGetPeers(BeMap beMap, InetSocketAddress socketAddress) {
        //log.debug("dealGetPeers {}",socketAddress);
        BeMap beObject = (BeMap)beMap.get("a");
        BeString id = (BeString)beObject.get("id");
        Node.createNode(id, socketAddress);
        //构建响应消息
        BeString infoHash = (BeString)beObject.get("info_hash");

        Bucket bucketT = getBucket(infoHash);
        Node byKey = bucketT.getByKey(infoHash);
        BeString tBs = (BeString)beMap.get("t");
        Message message;
        if (byKey != null) {
            message = MessageBuild.buildRespMsg()
                .buildRValues(byKey.getValues())
                .buildT(tBs)
                .buildRToken(XUtil.getRandomToken())
                .buildRespGetPeersOk(socketAddress);

        } else {
            BeString nodes = getNearNodes(bucketT);
            message = MessageBuild.buildRespMsg()
                .buildRNodes(nodes)
                .buildT(tBs)
                .buildRToken(XUtil.getRandomToken())
                .buildRespGetPeersNo(socketAddress);
        }
        sendCache.add(message);
    }

    /**
     * @param beMap
     * @param socketAddress
     */
    private void dealPing(BeMap beMap, InetSocketAddress socketAddress) {
      //  log.debug("dealPing {}",socketAddress);
        BeMap beObject = (BeMap)beMap.get("a");
        BeString id = (BeString)beObject.get("id");
        Node.createNode(id, socketAddress);
        //构建响应消息
        BeString tBs = (BeString)beMap.get("t");
        Message message = MessageBuild
            .buildRespMsg()
            .buildT(tBs)
            .buildRId(Const.thisId)
            .buildPingResp(socketAddress);
        sendCache.add(message);
    }

    /**
     * @param beMap
     * @param socketAddress
     */
    private void dealFindNode(BeMap beMap, InetSocketAddress socketAddress) throws ExecutionException {
      //  log.debug("dealFindNode {}",socketAddress);
        BeMap beObject = (BeMap)beMap.get("a");
        //先把请求者放入k桶
        BeString id = (BeString)beObject.get("id");
        Node.createNode(id, socketAddress);

        //在寻找特定节点
        BeString target = (BeString)beObject.get("target");
        Bucket bucketT = getBucket(target);
        BeString nodes = getNearNodes(bucketT);

        BeString tBs = (BeString)beMap.get("t");
        Message message = MessageBuild
            .buildRespMsg()
            .buildT(tBs)
            .buildRNodes(nodes)
            .buildRespFindNode(socketAddress);
        sendCache.add(message);
    }

    private Bucket getBucket(BeString target) {
        int kT = XUtil.distanceK(target, Const.thisId);
        return Const.routerTable.getBucket(kT);
    }

    /**
     * 从K桶中获得最相近的几个节点, 如果ｋ桶是空的，　则从其他桶获得数据
     *
     * @param bucketT
     * @return
     */
    private BeString getNearNodes(Bucket bucketT) {
        List<Node> list = new ArrayList<>();
        if (bucketT.isEmpty()) {
            //如果K桶是空的, 则把自己发出去
            list.add(Const.myNode);
        } else {
            list.addAll(bucketT.values());
        }

        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
        list.forEach(node1 -> {
            try {
                BeString id = node1.getId();
                byteOutputStream.write(id.getValue());
                InetAddress inetAddress = node1.getSocketAddress().getAddress();
                byte[] address = inetAddress.getAddress();
                byteOutputStream.write(address);
                short port = (short)node1.getSocketAddress().getPort();
                byte start = (byte)(port >>> 8);
                byteOutputStream.write(start);
                byte end = (byte)port;
                byteOutputStream.write(end);
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
        });
        return new BeString(byteOutputStream.toByteArray());
    }

}
